package com.lowcode.platform.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lowcode.platform.dto.FileUploadResponse;
import com.lowcode.platform.entity.SysFile;
import com.lowcode.platform.exception.BusinessException;
import com.lowcode.platform.mapper.FileMapper;
import com.lowcode.platform.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.util.UUID;

/**
 * 文件服务
 */
@Slf4j
@Service
public class FileService extends ServiceImpl<FileMapper, SysFile> {

    @Autowired
    private FileMapper fileMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Value("${file.upload-path}")
    private String uploadPath;

    @Value("${file.chunk-path}")
    private String chunkPath;

    /**
     * 普通文件上传
     */
    public FileUploadResponse uploadFile(MultipartFile file, Long userId) {
        try {
            // 生成文件名
            String originalFilename = file.getOriginalFilename();
            String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
            String fileName = UUID.randomUUID().toString() + extension;

            // 创建目录
            File uploadDir = new File(uploadPath);
            if (!uploadDir.exists()) {
                uploadDir.mkdirs();
            }

            // 保存文件
            String filePath = uploadPath + fileName;
            File dest = new File(filePath);
            file.transferTo(dest);

            // 计算MD5
            String md5 = calculateMD5(dest);

            // 保存文件记录
            SysFile sysFile = new SysFile();
            sysFile.setFileName(originalFilename);
            sysFile.setFilePath(filePath);
            sysFile.setFileSize(file.getSize());
            sysFile.setFileType(extension);
            sysFile.setMd5(md5);
            sysFile.setUploadUserId(userId);
            sysFile.setStatus(1);
            fileMapper.insert(sysFile);

            // 构造响应
            FileUploadResponse response = new FileUploadResponse();
            response.setFileId(sysFile.getId());
            response.setFileName(originalFilename);
            response.setFilePath(filePath);
            response.setFileUrl("/api/file/download/" + sysFile.getId());
            response.setFileSize(file.getSize());
            response.setMd5(md5);

            return response;
        } catch (IOException e) {
            log.error("文件上传失败", e);
            throw new BusinessException("文件上传失败");
        }
    }

    /**
     * 分片上传
     */
    public void uploadChunk(MultipartFile chunk, String md5, Integer chunkNumber, Integer totalChunks) {
        try {
            // 创建分片目录
            String chunkDir = chunkPath + md5 + "/";
            File dir = new File(chunkDir);
            if (!dir.exists()) {
                dir.mkdirs();
            }

            // 保存分片
            String chunkFilePath = chunkDir + chunkNumber;
            File chunkFile = new File(chunkFilePath);
            chunk.transferTo(chunkFile);

            // 记录已上传的分片
            String key = "file:chunk:" + md5;
            redisUtil.increment(key);

            log.info("分片上传成功: {}/{}, MD5: {}", chunkNumber, totalChunks, md5);
        } catch (IOException e) {
            log.error("分片上传失败", e);
            throw new BusinessException("分片上传失败");
        }
    }

    /**
     * 合并分片
     */
    public FileUploadResponse mergeChunks(String md5, String fileName, Integer totalChunks, Long userId) {
        try {
            // 检查是否所有分片都已上传
            String key = "file:chunk:" + md5;
            Object count = redisUtil.get(key);
            if (count == null || Integer.parseInt(count.toString()) < totalChunks) {
                throw new BusinessException("分片未全部上传完成");
            }

            // 检查文件是否已存在（秒传）
            LambdaQueryWrapper<SysFile> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(SysFile::getMd5, md5);
            SysFile existFile = fileMapper.selectOne(wrapper);
            if (existFile != null && new File(existFile.getFilePath()).exists()) {
                redisUtil.delete(key);
                
                FileUploadResponse response = new FileUploadResponse();
                response.setFileId(existFile.getId());
                response.setFileName(existFile.getFileName());
                response.setFilePath(existFile.getFilePath());
                response.setFileUrl("/api/file/download/" + existFile.getId());
                response.setFileSize(existFile.getFileSize());
                response.setMd5(existFile.getMd5());
                return response;
            }

            // 合并分片
            String extension = fileName.substring(fileName.lastIndexOf("."));
            String newFileName = UUID.randomUUID().toString() + extension;
            String filePath = uploadPath + newFileName;

            File mergedFile = new File(filePath);
            File uploadDir = new File(uploadPath);
            if (!uploadDir.exists()) {
                uploadDir.mkdirs();
            }

            try (FileOutputStream fos = new FileOutputStream(mergedFile)) {
                for (int i = 1; i <= totalChunks; i++) {
                    String chunkFilePath = chunkPath + md5 + "/" + i;
                    File chunkFile = new File(chunkFilePath);
                    
                    if (!chunkFile.exists()) {
                        throw new BusinessException("分片文件不存在: " + i);
                    }
                    
                    Files.copy(chunkFile.toPath(), fos);
                }
            }

            // 删除分片文件
            deleteChunks(md5, totalChunks);
            redisUtil.delete(key);

            // 保存文件记录
            SysFile sysFile = new SysFile();
            sysFile.setFileName(fileName);
            sysFile.setFilePath(filePath);
            sysFile.setFileSize(mergedFile.length());
            sysFile.setFileType(extension);
            sysFile.setMd5(md5);
            sysFile.setUploadUserId(userId);
            sysFile.setStatus(1);
            fileMapper.insert(sysFile);

            // 构造响应
            FileUploadResponse response = new FileUploadResponse();
            response.setFileId(sysFile.getId());
            response.setFileName(fileName);
            response.setFilePath(filePath);
            response.setFileUrl("/api/file/download/" + sysFile.getId());
            response.setFileSize(mergedFile.length());
            response.setMd5(md5);

            log.info("文件合并成功: {}", fileName);
            return response;
        } catch (IOException e) {
            log.error("文件合并失败", e);
            throw new BusinessException("文件合并失败");
        }
    }

    /**
     * 删除分片文件
     */
    private void deleteChunks(String md5, Integer totalChunks) {
        try {
            for (int i = 1; i <= totalChunks; i++) {
                String chunkFilePath = chunkPath + md5 + "/" + i;
                File chunkFile = new File(chunkFilePath);
                if (chunkFile.exists()) {
                    chunkFile.delete();
                }
            }
            
            // 删除分片目录
            File chunkDir = new File(chunkPath + md5);
            if (chunkDir.exists()) {
                chunkDir.delete();
            }
        } catch (Exception e) {
            log.error("删除分片文件失败", e);
        }
    }

    /**
     * 计算文件MD5
     */
    private String calculateMD5(File file) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] buffer = new byte[8192];
            int bytesRead;

            try (java.io.FileInputStream fis = new java.io.FileInputStream(file)) {
                while ((bytesRead = fis.read(buffer)) != -1) {
                    md.update(buffer, 0, bytesRead);
                }
            }

            byte[] digest = md.digest();
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception e) {
            log.error("计算MD5失败", e);
            return null;
        }
    }

    /**
     * 获取已上传的分片数量
     */
    public Integer getUploadedChunks(String md5) {
        String key = "file:chunk:" + md5;
        Object count = redisUtil.get(key);
        return count == null ? 0 : Integer.parseInt(count.toString());
    }

    /**
     * 检查文件是否存在（秒传）
     */
    public FileUploadResponse checkFile(String md5) {
        LambdaQueryWrapper<SysFile> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysFile::getMd5, md5);
        SysFile sysFile = fileMapper.selectOne(wrapper);

        if (sysFile != null && new File(sysFile.getFilePath()).exists()) {
            FileUploadResponse response = new FileUploadResponse();
            response.setFileId(sysFile.getId());
            response.setFileName(sysFile.getFileName());
            response.setFilePath(sysFile.getFilePath());
            response.setFileUrl("/api/file/download/" + sysFile.getId());
            response.setFileSize(sysFile.getFileSize());
            response.setMd5(sysFile.getMd5());
            return response;
        }

        return null;
    }
}
